Skip to main content
Version: V7

V6 to V7

Preliminary instructions

Move EF Core migrations

warning

Mandatory step to prepare the usage of the new architecture with dedicated projects for EF Core migrations.
See documentation

MoveMigrationsEF.ps1
function Move-MigrationFiles {
param (
[string]$SourceDir,
[string]$DestDir,
[string]$OldNamespace,
[string]$NewNamespace,
[string]$Label
)

if (-not (Test-Path $SourceDir)) {
Write-Host "`n$Label : source folder not found, skipping: $SourceDir" -ForegroundColor Yellow
return
}

$files = Get-ChildItem -Path $SourceDir -File -Filter "*.cs"
if ($files.Count -eq 0) {
Write-Host "`n$Label : no .cs files found in source folder, skipping." -ForegroundColor Yellow
Write-Host "Deleting old migration folder: $($SourceDir.FullName)" -ForegroundColor Cyan
Remove-Item -Path $SourceDir -Recurse -Force
Write-Host "Old migration folder deleted." -ForegroundColor Green
Write-Host "$Label : done." -ForegroundColor Green
return
}

Write-Host "`n$Label : moving $($files.Count) .cs file(s) from '$SourceDir' to '$DestDir'" -ForegroundColor Cyan

if (-not (Test-Path $DestDir)) {
New-Item -ItemType Directory -Path $DestDir -Force | Out-Null
}

foreach ($file in $files) {
$content = [System.IO.File]::ReadAllText($file.FullName)
$updatedContent = $content -replace [regex]::Escape($OldNamespace), $NewNamespace
$destFile = Join-Path $DestDir $file.Name
[System.IO.File]::WriteAllText($destFile, $updatedContent, [System.Text.Encoding]::UTF8)
Remove-Item -Path $file.FullName -Force
Write-Host " Moved & updated namespace: $($file.Name)" -ForegroundColor Green
}

Write-Host "Deleting old migration folder: $SourceDir" -ForegroundColor Cyan
Remove-Item -Path $SourceDir -Recurse -Force
Write-Host "Old migration folder deleted." -ForegroundColor Green

Write-Host "$Label : done." -ForegroundColor Green
}

function Move-EfMigrationsToProjects {
param (
[string]$SourceBackEnd
)

# Find the Infrastructure.Data project folder (direct child whose name ends with .Infrastructure.Data)
$infraDataFolder = Get-ChildItem -Path $SourceBackEnd -Directory |
Where-Object { $_.Name -match '\.Infrastructure\.Data$' } |
Select-Object -First 1

if ($null -eq $infraDataFolder) {
Write-Host "Infrastructure.Data project folder not found in $SourceBackEnd" -ForegroundColor Red
return
}

$infraDataProjectName = $infraDataFolder.Name
Write-Host "Found Infrastructure.Data project: $infraDataProjectName" -ForegroundColor Cyan

# SQL Server: Migrations -> *.Migrations.SqlServer\Migrations
Move-MigrationFiles `
-SourceDir (Join-Path $infraDataFolder.FullName "Migrations") `
-DestDir (Join-Path "$($infraDataFolder.FullName).Migrations.SqlServer" "Migrations") `
-OldNamespace "$infraDataProjectName.Migrations" `
-NewNamespace "$infraDataProjectName.Migrations.SqlServer.Migrations" `
-Label "SqlServer"

# PostgreSQL: MigrationsPostGreSql -> *.Migrations.PostgreSQL\Migrations
Move-MigrationFiles `
-SourceDir (Join-Path $infraDataFolder.FullName "MigrationsPostGreSql") `
-DestDir (Join-Path "$($infraDataFolder.FullName).Migrations.PostgreSQL" "Migrations") `
-OldNamespace "$infraDataProjectName.MigrationsPostGreSql" `
-NewNamespace "$infraDataProjectName.Migrations.PostgreSQL.Migrations" `
-Label "PostgreSQL"
}

Move-EfMigrationsToProjects -SourceBackEnd "C:\sources\Project\DotNet"

BIA Framework Migration

info

Run it automatically by clicking on Migrate button
or
Execute each step manually until step 3 - Apply Diff

warning

Mind to check the output logs to check any errors or missing deleted files

info
  • SOLUTION 1 : Merging rejected files

  • SOLUTION 2 : Analyzing rejected files - MANUAL MIGRATION ONLY

tip

Use the conflict resolution chapter to help you


For each Angular project :

V6_to_V7_Replacement.ps1
$Source = "C:\sources\Project";
$SourceBackEnd = $Source + "\DotNet"
$SourceFrontEnd = $Source + "\Angular\src"

$ExcludeDir = ('dist', 'node_modules', 'docs', 'scss', '.git', '.vscode', '.angular', '.dart_tool', 'bia-shared', 'bia-features', 'bia-domains', 'bia-core')

function ReplaceInProject {
param (
[string]$Source,
[string]$OldRegexp,
[string]$NewRegexp,
[string]$Include

)
Write-Host "ReplaceInProject $OldRegexp by $NewRegexp";
#Write-Host $Source;
#Write-Host $OldRegexp;
#Write-Host $NewRegexp;
#Write-Host $Filter;
ReplaceInProjectRec -Source $Source -OldRegexp $OldRegexp -NewRegexp $NewRegexp -Include $Include
}

function ReplaceInProjectRec {
param (
[string]$Source,
[string]$OldRegexp,
[string]$NewRegexp,
[string]$Include
)
foreach ($childDirectory in Get-ChildItem -Force -Path $Source -Directory -Exclude $ExcludeDir) {
ReplaceInProjectRec -Source $childDirectory.FullName -OldRegexp $OldRegexp -NewRegexp $NewRegexp -Include $Include
}

Get-ChildItem -LiteralPath $Source -File -Filter $Include | ForEach-Object {
$oldContent = [System.IO.File]::ReadAllText($_.FullName);
$found = $oldContent | select-string -Pattern $OldRegexp
if ($found.Matches) {
$newContent = $oldContent -Replace $OldRegexp, $NewRegexp
$match = $newContent -match '#capitalize#([a-z])'
if ($match) {
[string]$lower = $Matches[1]
[string]$upper = $lower.ToUpper()
[string]$newContent = $newContent -Replace "#capitalize#([a-z])", $upper
}
if ($oldContent -cne $newContent) {
Write-Host " => " $_.FullName
[System.IO.File]::WriteAllText($_.FullName, $newContent)
}
}
}
}

function GetPresentationApiFolder {
param (
[string]$SourceFolder
)
$folderPath = Get-ChildItem -Path $SourceFolder -Recurse | Where-Object { $_.PSIsContainer -and $_.FullName.EndsWith("Presentation.Api") -and -not $_.FullName.StartsWith("BIA.") }
if ($null -ne $folderPath -and $folderPath.Count -gt 0) {
$folderPath = $folderPath[0].FullName.ToString()
return $folderPath
}
else {
return $null
}
}

function InsertFunctionInClass() {
param (
[string]$Source,
[string]$MatchBegin,
[string]$FunctionBody,
[string]$ReplaceByExpr,
[string]$NoMatchCondition,
[string]$MatchCondition,
[string[]]$ReplaceSeqences,
[string[]]$ReplaceByMatch1
)
foreach ($childDirectory in Get-ChildItem -Force -Path $Source -Directory -Exclude $ExcludeDir) {
InsertFunctionInClassRec -Source $childDirectory.FullName -MatchBegin $MatchBegin -NoMatchCondition $NoMatchCondition -FunctionBody $FunctionBody -ReplaceByExpr $ReplaceByExpr -MatchCondition $MatchCondition -ReplaceSeqences $ReplaceSeqences -ReplaceByMatch1 $ReplaceByMatch1
}
}

function InsertFunctionInClassRec() {
param (
[string]$Source,
[string]$MatchBegin,
[string]$FunctionBody,
[string]$ReplaceByExpr,
[string]$NoMatchCondition,
[string]$MatchCondition,
[string[]]$ReplaceSeqences,
[string[]]$ReplaceByMatch1
)
foreach ($childDirectory in Get-ChildItem -Force -Path $Source -Directory -Exclude $ExcludeDir) {
InsertFunctionInClassRec -Source $childDirectory.FullName -MatchBegin $MatchBegin -NoMatchCondition $NoMatchCondition -FunctionBody $FunctionBody -ReplaceByExpr $ReplaceByExpr -MatchCondition $MatchCondition -ReplaceSeqences $ReplaceSeqences -ReplaceByMatch1 $ReplaceByMatch1
}

$fichiersTypeScript = Get-ChildItem -Path $Source -Filter "*.ts"
foreach ($fichier in $fichiersTypeScript) {
$contenuFichier = Get-Content -Path $fichier.FullName -Raw

# Vérifiez si la classe hérite de CrudItemService
if ($contenuFichier -match $MatchBegin) {
$nomClasse = $matches[1]
if ($MatchCondition -eq "" -or $contenuFichier -match $MatchCondition) {
# Vérifiez si les fonctions ne sont pas déjà présentes
if ($contenuFichier -notmatch $NoMatchCondition) {
# Utilisez une fonction pour trouver la position de la fermeture de la classe
$positionFermetureClasse = TrouverPositionFermetureClasse $contenuFichier $MatchBegin

$FunctionBodyRep = $FunctionBody;
For ($i = 0; $i -lt $ReplaceSeqences.Length; $i++) {
$ReplaceByMatch = $ReplaceByMatch1[$i]
if ($contenuFichier -match $ReplaceByMatch) {
$Match = $matches[1]
Write-Host "Replacement found : $ReplaceByMatch : $Match"
$FunctionBodyRep = $FunctionBodyRep.Replace($ReplaceSeqences[$i], $Match)
}
else {
Write-Host "Replacement not found : $ReplaceByMatch"
}
}
# Insérez les fonctions avant la fermeture de la classe
$contenuFichier = $contenuFichier.Insert($positionFermetureClasse, $FunctionBodyRep + " ")

# Écrivez les modifications dans le fichier
$contenuFichier | Set-Content -Path $fichier.FullName -NoNewline
Write-Host "Fonctions ajoutées à la classe ou namespace $nomClasse dans le fichier $($fichier.FullName)"
}
}
}
}
}

# Fonction pour trouver la position de la fermeture de la classe
function TrouverPositionFermetureClasse ($contenuFichier, $MatchBegin) {
$nombreAccoladesOuvrantes = 0
$nombreAccoladesFermantes = 0
$index = 0
$trouveClasse = $false
$positionFermeture = 0

# Parcourez le contenu du fichier ligne par ligne
$contenuFichier -split " " | ForEach-Object {

# Vérifiez si la ligne contient la déclaration de la classe
if ($trouveClasse -eq $false -and $_ -match $MatchBegin) {
$trouveClasse = $true
}

# Si la classe a été trouvée, mettez à jour les compteurs d'accolades
if ($trouveClasse) {
$nombreAccoladesOuvrantes += ($_ -split "{").Count - 1
$nombreAccoladesFermantes += ($_ -split "}").Count - 1
}

# Si le nombre d'accolades fermantes est égal au nombre d'accolades ouvrantes
# pour la classe en cours, retournez l'index actuel
if ($trouveClasse -and $nombreAccoladesFermantes -gt 0 -and $nombreAccoladesFermantes -eq $nombreAccoladesOuvrantes -and $positionFermeture -eq 0) {
$positionFermeture = $index
}
$index += $_.Length + 1
}

# Retournez la dernière position si la classe n'a pas de fermeture explicite
return $positionFermeture
}

function Invoke-ReplacementsInFiles {
param(
[Parameter(Mandatory)]
[string] $RootPath,
[Parameter(Mandatory)]
[hashtable[]] $Replacements, # @{ Pattern = '<regex>'; Replacement = '<string>'; Requirement = '<string>' }
[Parameter(Mandatory)]
[string[]] $Extensions
)
$excludeFullPaths =
$ExcludeDir | ForEach-Object {
$p = if ([System.IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path -Path $RootPath -ChildPath $_ }
[System.IO.Path]::GetFullPath($p)
}

$i = 0
$allFiles = Get-ChildItem -Path $RootPath -Recurse -File -Include $Extensions

$allTotal = $allFiles.Count

$files = $allFiles |
Where-Object {
$i++
Write-Progress -Activity "Filtering files to process..." -Status "Item $i of $allTotal" -PercentComplete (($i / $allTotal) * 100)
$full = [System.IO.Path]::GetFullPath($_.FullName)
$isExcluded = $false
foreach ($ex in $excludeFullPaths) {
if ($full.StartsWith($ex.TrimEnd('\','/'), [System.StringComparison]::OrdinalIgnoreCase)) {
$isExcluded = $true
break
}
}
-not $isExcluded
}

$total = $files.Count

$j = 0
$files | ForEach-Object {
$j++
Write-Progress -Activity "Processing files..." -Status "Item $j of $total" -PercentComplete (($j / $total) * 100)
$content = Get-Content -LiteralPath $_.FullName -Raw
$fileModified = $false
$fileReplacements = @()
$contentCurrent = $content

foreach ($rule in $Replacements) {
if($rule.Requirement -and -not ($contentCurrent -cmatch $rule.Requirement)) {
continue;
}

$newContent = $contentCurrent -creplace $rule.Pattern, $rule.Replacement
if ($newContent -cne $contentCurrent) {
$contentCurrent = $newContent
$fileModified = $true
$fileReplacements += " => replaced `"$($rule.Pattern)`" by `"$($rule.Replacement)`" ($occ)"
}
}

if ($fileModified) {
Write-Host $_.FullName -ForegroundColor Green
$fileReplacements | ForEach-Object { Write-Host $_ -ForegroundColor Yellow }
[System.IO.File]::WriteAllText($_.FullName, $contentCurrent, [System.Text.Encoding]::UTF8)
}
}
}

function Sync-BiaNetPermissions {
param(
[string]$DotNetPath = "DotNet"
)

$apiFolder = Get-ChildItem -Path $DotNetPath -Directory -Filter "*.Presentation.Api" | Select-Object -First 1
if (-not $apiFolder) {
Write-Error "No folder matching *.Presentation.Api found under $DotNetPath"
return
}

$ConfigPath = Join-Path $apiFolder.FullName "bianetconfig.json"
$PermissionsPath = Join-Path $apiFolder.FullName "bianetpermissions.json"

$configContent = Get-Content $ConfigPath -Raw -Encoding UTF8
$lines = $configContent -split "`n"

# Find the Permissions block boundaries
$startLine = -1
$depth = 0
$endLine = -1

for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i]
if ($startLine -eq -1) {
if ($line -match '^\s*"Permissions"\s*:\s*\[') {
$startLine = $i
foreach ($char in $line.ToCharArray()) {
if ($char -eq '[') { $depth++ }
elseif ($char -eq ']') { $depth-- }
}
if ($depth -eq 0) { $endLine = $i; break }
}
}
else {
foreach ($char in $line.ToCharArray()) {
if ($char -eq '[') { $depth++ }
elseif ($char -eq ']') { $depth-- }
}
if ($depth -eq 0) { $endLine = $i; break }
}
}

if ($startLine -eq -1 -or $endLine -eq -1) {
Write-Host "No Permissions block found in $ConfigPath, skipping."
return
}

# Write new bianetpermissions.json
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
$permissionsBlock = ($lines[$startLine..$endLine]) -join "`n"
$newPermissionsContent = "{`n `"BiaNet`": {`n $($permissionsBlock.Trim())`n }`n}`n"
[System.IO.File]::WriteAllText((Resolve-Path $PermissionsPath), $newPermissionsContent, $utf8NoBom)
Write-Host "Updated: $PermissionsPath"

# Remove Permissions block from bianetconfig.json
$beforeLines = if ($startLine -gt 0) { $lines[0..($startLine - 1)] } else { @() }
$afterLines = if ($endLine -lt ($lines.Count - 1)) { $lines[($endLine + 1)..($lines.Count - 1)] } else { @() }

# Remove trailing comma from the last non-empty line before the block
for ($i = $beforeLines.Count - 1; $i -ge 0; $i--) {
if ($beforeLines[$i].Trim() -ne '') {
$beforeLines[$i] = $beforeLines[$i] -replace ',\s*$', ''
break
}
}

# Collapse consecutive blank lines
$cleanedLines = @()
$prevBlank = $false
foreach ($line in ($beforeLines + $afterLines)) {
$isBlank = $line.Trim() -eq ''
if (-not ($isBlank -and $prevBlank)) { $cleanedLines += $line }
$prevBlank = $isBlank
}

[System.IO.File]::WriteAllText((Resolve-Path $ConfigPath), ($cleanedLines -join "`n"), $utf8NoBom)
Write-Host "Updated: $ConfigPath"
}

# FRONT END
# BEGIN - Ultima 21 css change
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "\blayout-container\b" -NewRegexp 'layout-wrapper' -Include "*.html"
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "\blayout-container\b" -NewRegexp 'layout-wrapper' -Include "*.ts"
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "\blayout-container\b" -NewRegexp 'layout-wrapper' -Include "*.scss"
# END - Ultima 21 css change

# BEGIN - Change feature singular name key
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "featureNameSingular:\s*'(?!app\.)(\S*)'" -NewRegexp 'featureNameSingular: ''app.$1''' -Include "*.ts"
# END - Change feature singular name key

# BEGIN - Replace specific input complex input by motion options
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp '\(onHide\)="onComplexInput\(false\)"' -NewRegexp '[motionOptions]="complexInputMotionOptions"' -Include "*.html"
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp '\(onPanelHide\)="onComplexInput\(false\)"' -NewRegexp '[motionOptions]="complexInputMotionOptions"' -Include "*.html"
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp '\(onPanelHide\)="onPanelHide\(\S*\)"' -NewRegexp '[motionOptions]="complexInputMotionOptions"' -Include "*.html"
# END - Replace specific input complex input by motion options

# BEGIN - Replace deprecated changeDetection for Angular 21
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "changeDetection:\s*ChangeDetectionStrategy\.Default" -NewRegexp 'changeDetection: ChangeDetectionStrategy.Eager' -Include "*.ts"
# END - Replace deprecated changeDetection for Angular 21

# BEGIN - Replace deprecated @primeng/themes by @primeuix/themes
ReplaceInProject ` -Source $SourceFrontEnd -OldRegexp "@primeng\/themes" -NewRegexp '@primeuix/themes' -Include "*.ts"
# END - Replace deprecated @primeng/themes by @primeuix/themes

# BACK END

# BEGIN - Move permissions to a dedicated file
Sync-BiaNetPermissions -DotNetPath $SourceBackEnd
# END - Move permissions to a dedicated file

# FRONT END CLEAN
Set-Location $SourceFrontEnd
npm run clean

# BACK END RESTORE
Set-Location $SourceBackEnd
dotnet restore --no-cache

Write-Host "Finish"
pause


tip

For manual management of conflicts case : you can remove the .rej files



Conflict Resolution

i18n files

Some translation keys have been moved to the bia i18n translation files for all features that are defined in bia-ng package. These translations concern:

  • Members
  • Notifications
  • Teams
  • Users

You can now override any bia translation key by your own key in the project i18n files (assets/i18n/app/en.json keys override assets/bia/i18n/en.json keys).

Most of the time you can remove from your project i18n files the translation keys concerning these four features during conflict resolution. If you customized a feature (like users) by adding some custom columns, you will need to keep these columns in your project files, but otherwise the whole block can be removed.

A fast way to do that would be to keep your previous i18n files during conflict resolution and then remove manually the four references to the four features in the app section and the four blocks concerning these features.

main.ts file

info

This conflict only occurs if you have previously modified the main.ts file in your project.

If you encounter a conflict on the main.ts file during the migration, the auto-resolve may not handle it properly.

The verifyPrimeNgLicence method has been moved to a different location in the framework. During conflict resolution, you need to remove this method from your main.ts file as it is no longer needed there.

To resolve this conflict:

  1. During merge conflict resolution, remove the verifyPrimeNgLicence method from your main.ts file
  2. Keep any other custom modifications you made to the file
  3. The method is now handled by the framework in its new location

Constants file

The Crosscutting.Common Constants class is now partial. You might have conflict on that file. You can check what has been moved by checking the Bia/Constants.cs file in the project, then remove everything that is already in that file during your conflict resolution. What has been moved in v7:

  • Application.FrameworkVersion
  • Application.Environment
  • DefaultValues.Theme
  • LanguageId.English
  • LanguageId.French
  • LanguageId.Spanish

Bianetconfig Permissions

Permissions will be moved from bianetconfig.json file to bianetpermissions.json file. You should have a conflict in the bianetconfig file. To resolve it: Keep your current changes in the permissions part of the bianetconfig The migration script will move your permissions to the new file automatically.

IocContainer

The IocContainer has largely been reworked. To facilitate future migrations, you should take the incoming change for the conflict resolution. After that, you will need to fix your IocContainer file during the manual steps of the migration.

Front Manual Steps

Extending CrudItemImportService

If you inherit CrudItemImportService in your application, you will see errors because it has now another contructor parameter. This parameter is "true" if you have the same DTO model for the list and for the form in your application (most cases) and "false" if you use different DTO models.

Back Manual Steps

IocContainer

To simplify future migrations, the IocContainer class has been completely redesigned.

A simple merge is not possible. The best approach is to take the new version and then reapply your project modifications. Please take the time to read the explanations below first.

1) Class structure

Before:

  • single class IocContainer in one file with all registrations.

Now:

  • IocContainer is public static partial class.
  • logic is split between two files:
    • TheBIADevCompany.BIADemo.Crosscutting.Ioc/Bia/IocContainer.cs This file is part of the framework and should never be modified
    • TheBIADevCompany.BIADemo.Crosscutting.Ioc/IocContainer.cs Only this file should contain your project customizations

2) Method signature

Before:

  • ConfigureContainer(IServiceCollection collection, IConfiguration configuration, bool isApi, bool isUnitTest = false).

Now:

  • ConfigureContainer(ParamIocContainer param).
  • dependencies and flags are grouped in ParamIocContainer (Collection, Configuration, IsApi, IsUnitTest, BiaNetSection, etc.).

3) Auto-registration

The new structure separates explicit registrations from assembly auto-registration:

  • BiaConfigureApplicationContainer + BiaConfigureApplicationContainerAutoRegister
  • BiaConfigureDomainContainer + BiaConfigureDomainContainerAutoRegister
  • BiaConfigureInfrastructureDataContainer + BiaConfigureInfrastructureDataContainerAutoRegister

If in your project you had specified ExcludedServiceNames or IncludedServiceNames, you now need to specify them in GetGlobalParamAutoRegister.

private static ParamAutoRegister GetGlobalParamAutoRegister(ParamIocContainer param)
{
return new ParamAutoRegister()
{
Collection = param.Collection,
ExcludedServiceNames = null, // Add here
IncludedServiceNames = null, // Add here
};
}

4) DbContext

The configuration of the DbContext (via collection.AddDbContext) has been moved to the method BiaConfigureInfrastructureDataContainerDbContext. This method is minimally configurable with the following input parameters:

  • string dbKey = BiaConstants.DatabaseConfiguration.DefaultKey
  • bool enableRetryOnFailure = true
  • int commandTimeout = default (30s)

ModelBuilder

This change was made to simplify future migrations. Please take the time to read the explanations below before starting the merge.

1) What changed

  • Model builders were refactored to split:
    • entity structure (Create...Model)
    • seed data (Create...ModelData)
  • CreateModel(...) now calls both structure and data methods explicitly.
  • BIA code moved into partial files (These files must never be modified):
    • ModelBuilders/Bia/UserModelBuilder.cs
    • ModelBuilders/Bia/TranslationModelBuilder.cs
  • Main model builder classes keep project-specific logic.

2) Main impacted classes

  • BaseUserModelBuilder
  • BaseTranslationModelBuilder
  • BaseNotificationModelBuilder
  • NotificationModelBuilder

3) Framework Migration

During framework migration, you must follow this pattern by separating the code that creates the entity structure from the code that initializes the data. You need to ensure that any code moved into the classes now contained in the ModelBuilders/Bia folder no longer appears in your ModelBuilders. If you do not wish to use the data initialization provided by BIA, you can comment out the code that starts with base..

4) Test Framework Migration

These changes should not result in any database migration. To verify this, here is what your migration file should look like at the end of your framework migration:

        protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTimeOffset>(
name: "CreatedDate",
table: "Notifications",
type: "datetimeoffset",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "datetime2");

migrationBuilder.AlterColumn<DateTimeOffset>(
name: "Start",
table: "Announcements",
type: "datetimeoffset",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "datetime2");

migrationBuilder.AlterColumn<DateTimeOffset>(
name: "End",
table: "Announcements",
type: "datetimeoffset",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "datetime2");

migrationBuilder.CreateTable(
name: "FileDownloadData",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
FileContentType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
FilePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
RequestDateTime = table.Column<DateTime>(type: "datetime2", nullable: false),
RequestByUserId = table.Column<int>(type: "int", nullable: false),
ExpiredAtDateTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_FileDownloadData", x => x.Id);
table.ForeignKey(
name: "FK_FileDownloadData_Users_RequestByUserId",
column: x => x.RequestByUserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});

migrationBuilder.CreateTable(
name: "FileDownloadTokens",
columns: table => new
{
FileGuid = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Token = table.Column<string>(type: "nvarchar(450)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FileDownloadTokens", x => new { x.FileGuid, x.Token });
table.ForeignKey(
name: "FK_FileDownloadTokens_FileDownloadData_FileGuid",
column: x => x.FileGuid,
principalTable: "FileDownloadData",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});

migrationBuilder.InsertData(
table: "NotificationTypes",
columns: new[] { "Id", "Code", "Label" },
values: new object[] { 6, "downloadr", "Download Ready" });

migrationBuilder.InsertData(
table: "NotificationTypeTranslations",
columns: new[] { "Id", "Label", "LanguageId", "NotificationTypeId" },
values: new object[,]
{
{ 601, "Téléchargement prêt", 2, 6 },
{ 602, "Descarga lista", 3, 6 }
});

migrationBuilder.CreateIndex(
name: "IX_FileDownloadData_RequestByUserId",
table: "FileDownloadData",
column: "RequestByUserId");
}